#region Using References

using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Drawing;
using Beaker.OpenCube.Game;
using Beaker.OpenCube.Textures;
using Beaker.Services.FileSystem;

#endregion

namespace Beaker.OpenCube.Dynamic
{
	/// <summary>
	/// Dynamicly process the requested texture pack data
	/// </summary>
	public sealed class DynamicTexturePack
		: ITexturePack
	{
		private readonly IFile _texturePackFilePath;
		private readonly IFile _fallbackTexturePackFilePath;
		private readonly TexturePackReader _reader;
		private readonly ImageCache _imageCache;

		internal DynamicTexturePack( IFile texturePackFilePath, IFile fallbackTexturePackFilePath = null )
		{
			Contract.Requires<ArgumentNullException>( texturePackFilePath != null );

			_texturePackFilePath = texturePackFilePath;
			_fallbackTexturePackFilePath = fallbackTexturePackFilePath;

			_imageCache = new ImageCache();

			_reader = TexturePackReader.FromFile( _texturePackFilePath, _fallbackTexturePackFilePath, _imageCache );
		}

		private sealed class ImageCache
			: IImageCache
		{
			private readonly WeakDictionary<string, Bitmap> _cache
				= new WeakDictionary<string, Bitmap>();

			/// <summary>
			/// Gets the image with the relative path from the cache
			/// </summary>
			/// <param name="relativePath">The relative path of the image to get</param>
			/// <returns>The image in the cache or <c>null</c> when it was not found in the cache</returns>
			public Bitmap GetImage( string relativePath )
			{
				Bitmap image;
				_cache.TryGetValue( relativePath.ToLowerInvariant(), out image );
				return image;
			}

			/// <summary>
			/// Add the given image to the cache
			/// </summary>
			/// <param name="relativePath">The relative path of the image</param>
			/// <param name="image">The image to cache</param>
			public void AddImage( string relativePath, Bitmap image )
			{
				_cache.Add( relativePath.ToLowerInvariant(), image );
			}
		}

		private string _name;

		/// <summary>
		/// Gets the name.
		/// </summary>
		/// <value>
		/// The name.
		/// </value>
		public string Name
		{
			get
			{
				if( _name == null )
					_name = _reader.ReadName();
				return _name;
			}
		}

		public Bitmap GetImage( string relativePath )
		{
			Bitmap cachedImage = _imageCache.GetImage( relativePath );
			if( cachedImage != null )
				return cachedImage;

			Bitmap image = _reader.TryReadImage( relativePath );
			if( image != null )
				_imageCache.AddImage( relativePath, image );

			return image;
		}

		private readonly WeakDictionary<Block, BlockTexture> _blockTextureCache =
			new WeakDictionary<Block, BlockTexture>();

		/// <summary>
		/// Get the texture for the given block
		/// </summary>
		/// <param name="block">The block to get the texture for</param>
		/// <returns>The texture for the requested block</returns>
		public BlockTexture GetBlockTexture( Block block )
		{
			// check if there is already a dictionary item
			BlockTexture blockTexture;
			if( !_blockTextureCache.TryGetValue( block, out blockTexture ) )
			{
				// create a new dictionary item.
				// load block texture
				blockTexture = _reader.ReadBlockTexture( block );
				// add to cache
				if( blockTexture != null )
					_blockTextureCache.Add( block, blockTexture );
			}

			return blockTexture;
		}

		/// <summary>
		/// Maps the BiomeTypes to pixel position in the colour mapping textures
		/// </summary>
		private static readonly Dictionary<BiomeColorType, Point> BiomeColourPositionMaping =
			new Dictionary<BiomeColorType, Point>
			{
				{ BiomeColorType.Jungle, new Point( 0, 25 ) },
				{ BiomeColorType.MushroomIsland, new Point( 25, 25 ) },
				{ BiomeColorType.Swampland, new Point( 50, 71 ) },
				{ BiomeColorType.Forest, new Point( 76, 112 ) },
				{ BiomeColorType.Default, new Point( 127, 127 ) },
				{ BiomeColorType.Plains, new Point( 50, 173 ) },
				{ BiomeColorType.Ocean, new Point( 127, 191 ) },
				{ BiomeColorType.ExtremeHills, new Point( 203, 239 ) },
				{ BiomeColorType.Taiga, new Point( 242, 244 ) },
				{ BiomeColorType.Desert, new Point( 0, 255 ) },
				{ BiomeColorType.IcePlains, new Point( 255, 255 ) }
			};

		private readonly Dictionary<BiomeColorBlockType, Dictionary<BiomeColorType, Color>> _biomeColors =
			new Dictionary<BiomeColorBlockType, Dictionary<BiomeColorType, Color>>();

		/// <summary>
		/// Gets a biome colouring of a given type in a given biome.
		/// </summary>
		/// <param name="biomeColorType">Type of the biome.</param>
		/// <param name="biomeColorBlockType">Type of the biome color block.</param>
		/// <returns>
		/// The colour of the block type in the requested biome.
		/// </returns>
		public Color GetBiomeColour( BiomeColorType biomeColorType, BiomeColorBlockType biomeColorBlockType )
		{
			// Contract.Requires<ArgumentOutOfRangeException>( Enum.IsDefined( typeof( BiomeColorType ), biomeColorType ) );
			// Contract.Requires<ArgumentOutOfRangeException>( Enum.IsDefined( typeof( BiomeColorBlockType ), biomeColorBlockType ) );

			// check if color texture already processed
			Dictionary<BiomeColorType, Color> dictionary;
			if( !_biomeColors.TryGetValue( biomeColorBlockType, out dictionary ) )
			{
				dictionary = new Dictionary<BiomeColorType, Color>();

				// not processed, load the texture
				string path;

				switch( biomeColorBlockType )
				{
					case BiomeColorBlockType.Foliage:
						path = "misc/foliagecolor.png";
						break;
					default:
					case BiomeColorBlockType.Grass:
						path = "misc/grasscolor.png";
						break;
					case BiomeColorBlockType.Water:
						path = "misc/watercolor.png";
						break;
				}

				Bitmap image = _reader.TryReadImage( path );
				if( image != null )
				{
					// now get all pixels
					foreach( KeyValuePair<BiomeColorType, Point> mapping in BiomeColourPositionMaping )
					{
						Color color = image.GetPixel( mapping.Value.X, mapping.Value.Y );
						dictionary.Add( mapping.Key, color );
					}

					_biomeColors.Add( biomeColorBlockType, dictionary );
				}
				else
				{
					// there are no biome colors
					return Color.Black;
				}
			}

			return dictionary[biomeColorType];
		}
	}
}